﻿#region Header File
//
// ByteStuffing.cs - Byte Stuffing Encoder/Decoder
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.Diagnostics;
using System.Collections.Generic;
#endregion

namespace Arq
{
    /// <summary>
    /// Byte Stuffing Encoder/Decoder. Supports COBS (Consistent Overhead Byte Stuffing) encoding
    /// See:
    /// Stuart Cheshire, Mary Baker, "Consistent Overhead Byte Stuffing", IEEE/ACM Transactions on 
    /// Networking, Vol. 7, No. 2, April 1999
    /// </summary>
    class ByteStuffing
    {
        #region Private Members
        Queue<byte> encQ;
        Queue<byte> decQ;

        byte[] encbuf; // encoder work buffer
        byte[] decbuf; // decoder work buffer
        int    enclen; // encoder work buffer size 
        int    declen; // decoder work buffer size
        int    zeroes; // zero count in decQ
        int    zcount; // number of bytes since the last 0x00
        #endregion

        #region Properties

        /// <summary>The MTU of the encoder</summary>
        public int  MaxFrameSize    { get; private set; }
        
        /// <summary>MaxFrameSize + COBS overhead</summary>
        public int  MaxBlockSize    { get; private set; }

        /// <summary>Whether there is frame available</summary>
        public bool FrameAvailable  { get; private set; }
        public int  FrameSize       { get { return declen;     } }
        public int  EncoderBytes    { get { return encQ.Count; } }
        public int  DecoderBytes    { get { return decQ.Count; } }
        #endregion

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="mtu">Maximum Transfer Unit (254 bytes max)</param>
        public ByteStuffing(int maxframesize)
        {
            Debug.Assert(maxframesize < 255);
            Debug.Assert(maxframesize > 000);

            encQ = new Queue<byte>();
            decQ = new Queue<byte>();

            MaxFrameSize = maxframesize;
            MaxBlockSize = MaxFrameSize + (int)Math.Ceiling((double)MaxFrameSize / 254);

            encbuf = new byte[MaxBlockSize + 1];
            decbuf = new byte[MaxBlockSize + 1];
            enclen = 0;
            declen = 0;
            zeroes = 0;
            zcount = 0;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Removes bytes from que output queue
        /// </summary>
        /// <param name="src"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        public void GetByte(out byte b)
        {
            b = encQ.Dequeue();
        }

        /// <summary>
        /// Enqueue bytes, detect and decode frames
        /// </summary>
        /// <param name="src"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        public void PutByte(/**/byte b)
        {
            zcount++;
            if ((b == 0) || (zcount > MaxBlockSize))
            {
                zeroes++;
                zcount = 0;
                b      = 0;
            }

            decQ.Enqueue(b);

            // Zero marks boundaries between frames. 
            // Decode the frame only if the decoder work buffer is empty
            while ((!FrameAvailable) && (zeroes > 0))
                Decode();          
        }
       
        /// <summary>
        /// Encode a frame
        /// </summary>
        /// <param name="src"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        public void PutFrame(/**/byte[] src)
        {
            Debug.Assert(src.Length <= MaxFrameSize);

            int  k    = 0;  // pointer to code postition
            int  d    = 1;  // pointer to next data position
            byte code = 1;  // code
            byte b;

            for (int i = 0; i < src.Length; i++)
            {
                b = src[i];

                if (b == 0)
                {
                    /* FinishBlock */
                    encbuf[k] = code; 
                    k         = d;
                    d++;
                    code      = 1;
                }
                else
                {
                    encbuf[d] = b;
                    d++;
                    code++;
                }
            }
            
            /* FinishBlock */
            encbuf[k] = code;
            enclen    = d;

            // enqueue the coded frame
            for (int i = 0; i < enclen; i++)
                encQ.Enqueue(encbuf[i]);

            // Finish/Separate frames with 0x00
            encQ.Enqueue(0x00);
        }

        /// <summary>
        /// Gets a decoded frame
        /// </summary>
        /// <returns></returns>
        public void GetFrame(out byte[] dst)
        {
            if (!FrameAvailable)
            {
                dst = null;
                return;
            }

            dst = new byte[declen];
            Array.Copy(decbuf, dst, declen);
            
            // empty decoder work buffer
            declen = 0;
            FrameAvailable = false;

            // Is thre more data at queue? If yes, 
            // the enqueued frames could be bad-formed
            // try to decode frame after frame until sucess (declen > 0)
            while ((!FrameAvailable) && (zeroes > 0))
                Decode();
        }

        /// <summary>
        /// Clear byte queues and reset internal state
        /// </summary>
        public void Reset()
        {
            encQ.Clear();
            decQ.Clear();
        }
        #endregion

        #region Private Members


        /// <summary>
        /// Executed when in the decoder queue of bytes has enought material to decode a frame
        /// let's process it. Caution: the sequence of bytes could be maliciously, bad-formed.
        /// The algorithm must take into acount patological cr9k1ng s3qu3nc3s.
        /// Warning the value of declen variable is only meaninfull when FrameAvailable = true
        /// </summary>
        private void Decode()
        {
            byte code, b;

            declen = 0;
            // The queue must contain a frame byte sequence terminated with 0x00
            while (true)
            {
                // decode a block [code b_1 b_2 b_3 ... ]
                if ((code = decQ.Dequeue()) == 0)
                {
                    zeroes--;                    
                    if (--declen > 0) 
                        FrameAvailable = true;
                    return;                 
                }

                while (code-- > 1)
                {
                    if ((b = decQ.Dequeue()) == 0)
                    {
                        zeroes--;
                        return;
                    }

                    decbuf[declen++] = b;
                }

                decbuf[declen++] = 0x00; // finish each block with 0x00
            }
        }
        #endregion
    }
}
